package com.yuneec.droneservice.protocol.analysis;

import com.yuneec.droneservice.tools.RCLog;
import com.yuneec.droneservice.utils.ByteUtils;
import com.yuneec.droneservice.utils.CrcUtils;

import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * Created by zd.zhang on 2017/10/26.
 */

public class IdealAnalysis extends Analysis {
    private LinkedBlockingQueue<ByteBuf> onReceive;
    private LinkedBlockingQueue<ByteBuf> onAnalysis;
    private byte[] abandon;
    private final int CACHE_SIZE;

    public IdealAnalysis(int cachePackageCount, int perCachePackageSize) {
        super();
        this.CACHE_SIZE = perCachePackageSize;
        onReceive = new LinkedBlockingQueue<>(cachePackageCount);
        onAnalysis = new LinkedBlockingQueue<>(cachePackageCount);
        for (int i = 0; i < cachePackageCount; i++) {
            onReceive.offer(new ByteBuf(CACHE_SIZE));
        }
        abandon = new byte[CACHE_SIZE];
    }


    @Override
    public void read(InputStream is) throws IOException {
        try {
            ByteBuf buf = onReceive.poll();
            if (buf == null) {
                is.read(abandon);
            } else {
                buf.length = is.read(buf.buf);
                onAnalysis.offer(buf);
            }
        } catch (Exception e) {
            is.read(abandon);
        }
    }


    @Override
    protected void onStart() {
        super.onStart();
        run(new Runnable() {
            @Override
            public void run() {
                try {
                    analysis();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    private int state = STATE_NONE;
    private int lack = 0;
    private static final int STATE_NONE = 0;
    private static final int STATE_HAS_2_66 = 1;
    private static final int STATE_HAS_1_66 = 2;
    private static final int STATE_LACK_BODY = 3;
    private Message lackMsg;
    private byte[] header = new byte[]{PROTOCOL_HEADER_FLAG, PROTOCOL_HEADER_FLAG, 0, 0, 0, 0, 0, 0};

    private void analysis() throws InterruptedException {
        ByteBuf byteBuffer = onAnalysis.poll(1000, TimeUnit.MILLISECONDS);
        if (byteBuffer == null) {
            return;
        }
        try {
//            byteBuffer.flip();
//            int length = byteBuffer.remaining();
            int length = byteBuffer.length;
            if (length <= 0) {
                return;
            }
//            byte[] buf = byteBuffer.array();
            byte[] buf = byteBuffer.buf;
            switch (state) {
                case STATE_NONE:
                    readAll(buf, 0, length);
                    break;
                case STATE_HAS_2_66:
                    lackHeader(buf, length);
                    break;
                case STATE_HAS_1_66:
                    if (buf[0] != PROTOCOL_HEADER_FLAG) {
                        lack = 0;
                        state = STATE_NONE;
                        readAll(buf, 0, length);
                    } else {
                        lackHeader(buf, length);
                    }
                    break;
                case STATE_LACK_BODY:
                    lackBody(buf, length);
                    break;
            }
        } finally {
//            byteBuffer.clear();
            onReceive.offer(byteBuffer);
        }
    }

    private void readAll(byte[] buf, int offset, int length) {
        while (length > 0) {
            int shiftOffset = ByteUtils.simpleFind(buf, offset, length, PROTOCOL_HEADER);
            if (shiftOffset < 0) {
                if (buf[offset + length - 1] == PROTOCOL_HEADER_FLAG) {
                    state = STATE_HAS_1_66;
                    lack = 7;
                }
                return;
            }
            offset += shiftOffset;
            length -= shiftOffset;
            if (length < 8) {
                state = STATE_HAS_2_66;
                lack = 8 - length;
                return;
            }
            copyHeader(buf, offset);
            if (checkHeaderCRC()) {
                offset += 8;
                length -= 8;
                int len = sendMsg(buf, offset, length);
                offset += len;
                length -= len;
            } else {
                RCLog.d( "crc error!");
                offset++;
                length--;
            }
        }
    }

    private boolean checkHeaderCRC() {
        return CrcUtils.crc8Table(header, 0, 7) == header[7];
    }

    private void copyHeader(byte[] buf, int offset) {
        for (int i = 2; i < 8; i++) {
            header[i] = buf[offset + i];
        }
    }

    private int sendMsg(byte[] buf, int offset, int length) {
        int dataLen = getUShort(header, 2);
        int id = getUShort(header, 4);
        byte retain = header[6];
        MessageHandler handler = getMessageHandler(id);
        if (handler != null) {
            Message msg = obtainMessage();
            msg.init(id, dataLen, retain);
            if (length < dataLen) {
                state = STATE_LACK_BODY;
                System.arraycopy(buf, offset, msg.body(), 0, length);
                lackMsg = msg;
                lackMsg.setTarget(handler);
                lack = dataLen - length;
                return length;
            } else {
                System.arraycopy(buf, offset, msg.body(), 0, dataLen);
                handler.handle(msg);
                return dataLen;
            }
        }
        return Math.min(dataLen, length);
    }

    private void lackHeader(byte[] buf, int length) {
        int makeUpLen = makeUpHeader(buf, 0, length);
        if (lack > 0) {
            state = STATE_HAS_2_66;
            return;
        }
        if (checkHeaderCRC()) {
            int offset = makeUpLen;
            length -= makeUpLen;
            int readLen = sendMsg(buf, offset, length);
            if (lack > 0) {
                state = STATE_LACK_BODY;
            } else {
                offset += readLen;
                length -= readLen;
                state = STATE_NONE;
                readAll(buf, offset, length);
            }
        } else {
            lack = 0;
            state = STATE_NONE;
            readAll(buf, 1, length - 1);
        }

    }

    private void lackBody(byte[] buf, int length) {
        int readLen = Math.min(lack, length);
        System.arraycopy(buf, 0, lackMsg.body(), lackMsg.length() - lack, readLen);
        if (readLen >= lack) {
            lackMsg.sendToTarget();
            lackMsg = null;
            lack = 0;
            state = STATE_NONE;
            readAll(buf, readLen, length - readLen);
        } else {
            lack -= readLen;
        }
    }

    private int makeUpHeader(byte[] buf, int offset, int length) {
        int fl = Math.min(lack, length);
        int index = 8 - lack;
        for (int i = 0; i < fl; i++) {
            header[index + i] = buf[offset + i];
        }
        lack -= fl;
        return fl;
    }

    private int getUShort(byte[] src, int off) {
        if (src.length - off < 2) {
            throw new ArrayIndexOutOfBoundsException();
        }
        byte iLow = src[off];
        byte iHigh = src[off + 1];
        return ((iHigh & 0xFF) << 8) | (0xFF & iLow);
    }

    @Override
    public void clear() {
        throw new RuntimeException("This Analysis is not support!");
    }
}
